package net.hkbn.prov.helper;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.activation.DataSource;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.InputStreamSource;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;

public class SendMailHelper {

	private boolean isContentHtml;
	private String mailHost;
	private String mailFrom;
	private String mailPersonal;
	private String mailTo;
	private String mailCc;
	private String mailBcc;
	private String mailSubject;
	private String mailContent;
	private String mailUserName;
	private String mailUserPass;
	private Integer mailPort;
	private Map fileAttachMap;
	private Map stringAttachMap;
	private Map byteArrayOutputStreamMap;
	private Map embedImageMap;
	private static Pattern quoationPattern = Pattern.compile("\"(.*)\"");
	private static Pattern sharpBracketPattern = Pattern
			.compile("(.*)\\<(.*)\\>");
	
	// - 
	protected Map headers = new HashMap();
	protected String bounceAddress;
	
	public String getBounceAddress() {
		return bounceAddress;
	}

	public void setBounceAddress(String bounceAddress) {
		this.bounceAddress = bounceAddress;
	}

	
	public Map getHeaders() {
		return headers;
	}

	public void setHeaders(Map headers) {
		this.headers.clear();
		this.headers = headers;
	}
	
	
	public SendMailHelper() {
		fileAttachMap = null;
		stringAttachMap = null;
		byteArrayOutputStreamMap = null;
		isContentHtml = false;
		embedImageMap = null;
	}

	// remove double quotation from the string
	private String removeQuotation(String srcStr) {
		String desStr = srcStr;
		if (srcStr != null) {
			Matcher quoationMatcher = quoationPattern.matcher(srcStr);
			if (quoationMatcher.matches()) {
				desStr = quoationMatcher.group(1);
			}
		}
		return desStr;
	}

	private void filterAllQuotation() {
		this.mailFrom = removeQuotation(this.mailFrom);
		this.mailTo = removeQuotation(this.mailTo);
		this.mailCc = removeQuotation(this.mailCc);
		this.mailBcc = removeQuotation(this.mailBcc);
		this.mailSubject = removeQuotation(this.mailSubject);
	}

	/**
	 * Set the host of sendMail server.
	 * 
	 * @param mailHost
	 *            The host of sendMail server
	 */
	public void setMailHost(String mailHost) {
		this.mailHost = mailHost;
	}

	/**
	 * Set email sender.
	 * 
	 * @param mailFrom
	 *            The email sender
	 */
	public void setMailFrom(String mailFrom) {
		this.mailFrom = mailFrom;
	}

	public void setMailPersonal(String mailPersonal) {
		this.mailPersonal = mailPersonal;
	}

	/**
	 * Set email recepient.
	 * 
	 * @param mailTo
	 *            The email recepient
	 */
	public void setMailTo(String mailTo) {
		this.mailTo = mailTo;
	}

	/**
	 * Set email copy.
	 * 
	 * @param mailCc
	 *            The email copy
	 */
	public void setMailCc(String mailCc) {
		this.mailCc = mailCc;
	}
	
	/**
	 * Set email Blind Carbon Copy.
	 * 
	 * @param mailBcc
	 *            The email Blind Carbon Copy
	 */
	public void setMailBcc(String mailBcc) {
		this.mailBcc = mailBcc;
	}

	/**
	 * Set email subject.
	 * 
	 * @param mailSubject
	 *            The email subject
	 */
	public void setMailSubject(String mailSubject) {
		this.mailSubject = mailSubject;
	}
	
	public String getMailSubject() {
		return mailSubject;
	}
	

	/**
	 * Set email content with String format.
	 * 
	 * @param mailContent
	 *            If email content set to HTML syntax, the mailContent will be
	 *            interpretted by HTML syntax, else the mailContent string will
	 *            be directly put into the email content.
	 */
	public void setMailContent(String mailContent) {
		this.mailContent = mailContent;
	}

	/**
	 * Set if the email content is in HTML syntax.
	 * 
	 * @param isContentHtml
	 *            true or false, default is false
	 * @return void
	 */
	public void setContentHtml(boolean isContentHtml) {
		this.isContentHtml = isContentHtml;
	}

	/**
	 * Set email attachment with File format, if this method is called multiple
	 * times, then the files will be put into a eamil-attachmet list.
	 * 
	 * @param fileName
	 *            File name of the email attachment
	 * @param file
	 *            The email attachmet file
	 * @return void
	 */
	public void setAttachment(String fileName, File file) {
		if (fileAttachMap == null) {
			fileAttachMap = new HashMap();
		}
		fileAttachMap.put(fileName, file);
	}

	/**
	 * Set email attachment with String format, if this method is called
	 * multiple times, then the files will be put into a eamil-attachmet list.
	 * 
	 * @param fileName
	 *            File name of the email attachment
	 * @param inputStr
	 *            String with the email attachmet content
	 * @return void
	 */
	public void setAttachment(String fileName, String inputStr) {
		if (stringAttachMap == null) {
			stringAttachMap = new HashMap();
		}
		stringAttachMap.put(fileName, inputStr);
	}
	
	public void setAttachment(String fileName, ByteArrayOutputStream outputStrm) {
		if (byteArrayOutputStreamMap == null) {
			byteArrayOutputStreamMap = new HashMap();
		}
		byteArrayOutputStreamMap.put(fileName, outputStrm);
	}

	public void setEmbedImage(String imageId, File imageFile) {
		if (embedImageMap == null) {
			embedImageMap = new HashMap();
		}
		embedImageMap.put(imageId, imageFile);
	}
	
	public void setEmbedImage(String imageId,  DataSource dataSource){
		if (embedImageMap == null) {
			embedImageMap = new HashMap();
		}
		embedImageMap.put(imageId, dataSource);
	}
	
	public void setMailUserName(String mailUserName) {
		this.mailUserName = mailUserName;
	}

	public void setMailUserPass(String mailUserPass) {
		this.mailUserPass = mailUserPass;
	}

	public void setMailPort(Integer mailPort) {
		this.mailPort = mailPort;
	}

	private void handleSenderName(String fromString) {
		Matcher matcher = sharpBracketPattern.matcher(fromString);
		String personal = "";
		this.mailPersonal = "";
		if (matcher.matches()) {
			/* if exist sharp-bracket, then extra sender email address from it */
			this.mailFrom = matcher.group(2);
			personal = matcher.group(1);

			/* reset sender personal if not set value for mailPersonal before */
			if (this.mailPersonal == null || "".equals(this.mailPersonal)) {
				this.mailPersonal = personal;
			}
		} else {			/*
			 * if not exist sharp-bracket, then use whole fromString as sender
			 * email address
			 */
			
			this.mailFrom = fromString;
		}
	}

	/**
	 * Assemble the email elements and send out.
	 * 
	 * @return true or false
	 */
	public boolean send() {
		boolean result = false;
		
		try {
			JavaMailSenderImpl sender = new JavaMailSenderImpl();
			sender.setHost(this.mailHost);
			
			if (this.mailUserName != null) {
				sender.setUsername(mailUserName);
			}
			
			if (this.mailUserPass != null) {
				sender.setPassword(this.mailUserPass);
			}
			
			if (this.mailPort != null) {
				sender.setPort(this.mailPort);
			}
			
			filterAllQuotation();
			handleSenderName(this.mailFrom);
			// - Set properties Return-Path 
			setBounceBackEmail(sender);
			 
			MimeMessage message = sender.createMimeMessage();
			
			try {
				// - Set headers
				if (this.headers.size() > 0) {
					Iterator iterHeaderKeys = this.headers.keySet().iterator();
					while (iterHeaderKeys.hasNext()) {
						String name = (String) iterHeaderKeys.next();
						String value = (String) headers.get(name);
						message.addHeader(name, value);
					}
				}				
				
				MimeMessageHelper helper = new MimeMessageHelper(message, true,
						"UTF-8");
				helper.setReplyTo(mailFrom);
				try {
					helper.setFrom(this.mailFrom, this.mailPersonal);
				} catch (UnsupportedEncodingException e) {
					e.printStackTrace();
					return result;
				}
				if (this.mailTo != null) {
					String[] recipient = this.mailTo.split(",");
					for (int i = 0; i < recipient.length; i++) {
						helper.addTo(recipient[i]);
					}
				}
				if (this.mailCc != null) {
					String[] ccList = this.mailCc.split(",");
					for (int i = 0; i < ccList.length; i++) {
						// To avoid some Email config without "Cc" list
						if (ccList[i] != null && !"".equals(ccList[i])) {
							helper.addCc(ccList[i]);
						}
					}
				}
				if (this.mailBcc != null) {
					String[] bccList = this.mailBcc.split(",");
					for (int i = 0; i < bccList.length; i++) {
						// To avoid some Email config without "Bcc" list
						if (bccList[i] != null && !"".equals(bccList[i])) {
							helper.addBcc(bccList[i]);
						}
					}
				}
				helper.setSubject(this.mailSubject);
				if (this.mailContent != null && !"".equals(this.mailContent)) {
					if (isContentHtml) {
						helper.setText(this.mailContent, true);					
						// set embed image for HTML email content
						addEmbedImageToHtmlEmailContent(helper);
					} else {
						helper.setText(this.mailContent);
					}
				}
				// String format Attachment
				if (stringAttachMap != null && stringAttachMap.size() > 0) {
					Iterator fileKey = stringAttachMap.keySet().iterator();
					while (fileKey.hasNext()) {
						String attachFileName = (String) fileKey.next();
						String attachStream = (String) stringAttachMap
								.get(attachFileName);
						InputStreamSource inputStreamSource = new ByteArrayResource(
								attachStream.getBytes());
						try {
							helper.addAttachment(attachFileName, inputStreamSource);
						} catch (MessagingException e) {
							e.printStackTrace();
							return result;
						}
					}
				}
	
				//ByteArrayOutputStream format attachment(such as Excel)
				if(byteArrayOutputStreamMap != null && byteArrayOutputStreamMap.size() > 0) {
					Iterator fileKey = byteArrayOutputStreamMap.keySet().iterator();
					while (fileKey.hasNext()) {
						String attachFileName = (String) fileKey.next();
						ByteArrayOutputStream outputStrm = (ByteArrayOutputStream)byteArrayOutputStreamMap.get(attachFileName);
						InputStreamSource streamSource = new ByteArrayResource(outputStrm.toByteArray());
						try {
							helper.addAttachment(attachFileName, streamSource);
						} catch (MessagingException e) {
							e.printStackTrace();
							return result;
						}
					}
				}
				
				// File format Attachment
				if (fileAttachMap != null && fileAttachMap.size() > 0) {
					Iterator fileKey = fileAttachMap.keySet().iterator();
					while (fileKey.hasNext()) {
						String attachFileName = (String) fileKey.next();
						File attachFile = (File) fileAttachMap.get(attachFileName);
						try {
							helper.addAttachment(attachFileName, attachFile);
						} catch (MessagingException e) {
							e.printStackTrace();
							return result;
						}
					}
				}
			} catch (MessagingException m) {
				m.printStackTrace();
				return result;
			}
			try {
				sender.send(message);
			} catch (MailException m) {
				m.printStackTrace();
				return result;
			}
			result = true;
			return result;
		} finally {
			if (fileAttachMap != null) {
				fileAttachMap.clear();
			}
			if (stringAttachMap != null) {
				stringAttachMap.clear();
			}
			if (byteArrayOutputStreamMap != null) {
				byteArrayOutputStreamMap.clear();
			}
			if (embedImageMap != null) {
				embedImageMap.clear();
			}
			if(headers.size()>0){
				headers.clear();
			}
		}
	}

	/**
	 * @param sender
	 */
	private void setBounceBackEmail(JavaMailSenderImpl sender) {
		Properties properties = new Properties(System.getProperties());
		if (this.bounceAddress != null && !("".equals(this.bounceAddress))) {
			properties.setProperty("mail.smtp.from", this.bounceAddress);
			sender.setJavaMailProperties(properties);
		}
	}

	/**
	 * @param helper
	 * @throws MessagingException
	 */
	private void addEmbedImageToHtmlEmailContent(MimeMessageHelper helper) throws MessagingException {
		if ((embedImageMap != null) && (embedImageMap.size() > 0)) {
			Iterator imgIdIte = embedImageMap.keySet().iterator();
			while (imgIdIte.hasNext()) {
				String imgId = (String) imgIdIte.next();				
				if(embedImageMap.get(imgId) instanceof DataSource){
					DataSource dataSource = (DataSource)embedImageMap.get(imgId);
					helper.addInline(imgId, dataSource);
				}else if(embedImageMap.get(imgId) instanceof File){
					helper.addInline(imgId, (File)embedImageMap.get(imgId));								
				}
			}
		}
	}
}
